package cn.jw.starter.common.scheduletask;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTask;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.config.TriggerTask;
import org.springframework.scheduling.support.CronTrigger;

import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.jw.starter.common.JwCommonConfig;
import cn.jw.starter.common.scheduletask.model.ScheduledTaskInfo;
import cn.jw.starter.common.scheduletask.model.TaskProxyRunnable;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class ScheduledTaskCore implements SchedulingConfigurer {
    private ScheduledTaskRegistrar taskRegistrar;
    private ScheduledThreadPoolExecutor executorService;
    private final Map<String, ScheduledTask> taskClass2Task = new HashMap<>();

    private static final String THREAD_NAME_PREFIX = "jw-scheduled-task-pool";

    public ScheduledTaskCore() {
        Runtime.getRuntime().addShutdownHook(new Thread() {

            @Override
            public void run() {
                super.run();
                closeThreadPool();
            }
        });
    }

    private void closeThreadPool() {
        log.info("关闭定时任务线程池");
        boolean success = true;
        if (executorService != null) {
            executorService.shutdown();
            log.info("开始关闭定时任务线程池");
            executorService.shutdown();
            try {
                success = executorService.awaitTermination(1, TimeUnit.MINUTES);
            } catch (InterruptedException e) {
                log.error("关闭线程池等待中断", e);
            }
        }
        log.info("-----定时任务线程池关闭成功: {} ----", success);
    }

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        this.taskRegistrar = taskRegistrar;
        JwCommonConfig jwCommonConfig = SpringUtil.getBean(JwCommonConfig.class);
        int threadCount = jwCommonConfig.getScheduleTaskThreadCount();
        this.executorService = (ScheduledThreadPoolExecutor)Executors.newScheduledThreadPool(threadCount,
            ThreadUtil.newNamedThreadFactory(THREAD_NAME_PREFIX, false));
        taskRegistrar.setScheduler(executorService);
    }

    public void injectTaskToSpring(ScheduledTaskInfo taskInfo) {
        final Runnable runnable = findBean(taskInfo.getRunnableClass());
        if (runnable != null) {
            injectTaskToSpring(runnable, taskInfo);
        } else {
            log.error("SCHEDULED_TASK-无法从Spring容器找到该Bean, runnableClass = {}, taskName = {}ss",
                taskInfo.getRunnableClass(), taskInfo.getTaskName());
        }
    }

    private <T extends Runnable> T findBean(Class<T> runnableClass) {
        final Map<String, T> runnableMap = SpringUtil.getBeansOfType(runnableClass);
        if (runnableMap != null && runnableMap.size() > 0) {
            for (T runnable : runnableMap.values()) {
                return runnable;
            }
        }
        return null;
    }

    /**
     * 注入定时任务到Spring
     *
     * @param runnable
     * @param taskInfo
     */
    private void injectTaskToSpring(Runnable runnable, ScheduledTaskInfo taskInfo) {
        taskRegistrar.addTriggerTask(new TaskProxyRunnable(runnable), triggerContext -> {
            // 刷新cron
            final String cronTriggerExpression = taskInfo.getCronTriggerExpression();
            final CronTrigger cronTrigger = new CronTrigger(cronTriggerExpression);
            final Date nextExecDate = cronTrigger.nextExecutionTime(triggerContext);
            return nextExecDate.toInstant();
        });

        final List<TriggerTask> triggerTaskList = taskRegistrar.getTriggerTaskList();
        final TriggerTask triggerTask = triggerTaskList.get(triggerTaskList.size() - 1);
        final ScheduledTask scheduledTask = taskRegistrar.scheduleTriggerTask(triggerTask);

        final String key = runnable.getClass().getName();
        taskClass2Task.put(key, scheduledTask);

        try {
            final Method addScheduledTaskMethod =
                taskRegistrar.getClass().getDeclaredMethod("addScheduledTask", ScheduledTask.class);
            addScheduledTaskMethod.setAccessible(true);
            addScheduledTaskMethod.invoke(taskRegistrar, scheduledTask);
            log.info("SCHEDULED_TASK-注入定时任务到Spring成功, runnable = {}, taskName = {}, cronTriggerExpression = {}",
                taskInfo.getRunnableClass(), taskInfo.getTaskName(), taskInfo.getCronTriggerExpression());
        } catch (Exception e) {
            log.error("SCHEDULED_TASK-注入定时任务到Spring失败, runnableClass = {}, taskName = {}", taskInfo.getRunnableClass(),
                taskInfo.getTaskName());
        }
    }

    /**
     * 更新定时任务，需要先取消，然后重新添加，解决实时生效问题，否则必须等到旧的定时任务触发后才会使用新的Cron配置
     * 
     * @param taskInfo
     * @param <T>
     */
    protected <T extends Runnable> void updateScheduledTask(ScheduledTaskInfo taskInfo) {
        final Class runnableClass = taskInfo.getRunnableClass();
        canalScheduledTask(runnableClass);
        injectTaskToSpring(taskInfo);
    }

    // 取消定时任务
    protected <T extends Runnable> void canalScheduledTask(Class<T> runnableClass) {
        final String key = runnableClass.getName();
        final ScheduledTask scheduledTask = taskClass2Task.remove(key);
        if (scheduledTask != null) {
            try {
                scheduledTask.cancel();
            } catch (Exception e) {
                log.error("SCHEDULED_TASK-取消定时任务失败, runnableClass = {}", key, e);
            }

            try {
                removeScheduledTasksFromSpring(scheduledTask);
                removeScheduledTasksFromSpring(runnableClass);
            } catch (Exception e) {
                log.error("SCHEDULED_TASK-从Spring中删除Task失败, runnableClass = {}", key, e);
            }
        }
    }

    private <T extends Runnable> void removeScheduledTasksFromSpring(Class<T> runnableClass)
        throws NoSuchFieldException, IllegalAccessException {
        final Field triggerTasksField = taskRegistrar.getClass().getDeclaredField("triggerTasks");
        triggerTasksField.setAccessible(true);
        final List<TriggerTask> triggerTasks = (List<TriggerTask>)triggerTasksField.get(taskRegistrar);
        if (triggerTasks != null) {
            final Runnable runnable = findBean(runnableClass);
            if (runnable != null) {
                synchronized (triggerTasks) {
                    for (TriggerTask triggerTask : triggerTasks) {
                        if (triggerTask.getRunnable() != null
                            && triggerTask.getRunnable() instanceof TaskProxyRunnable) {
                            final TaskProxyRunnable proxyRunnable = (TaskProxyRunnable)triggerTask.getRunnable();
                            if (proxyRunnable.getRunnable() != null) {
                                if (proxyRunnable.getRunnable() == runnable) {
                                    triggerTasks.remove(triggerTask);
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    private void removeScheduledTasksFromSpring(ScheduledTask scheduledTask)
        throws NoSuchFieldException, IllegalAccessException {
        final Field scheduledTasksField = taskRegistrar.getClass().getDeclaredField("scheduledTasks");
        scheduledTasksField.setAccessible(true);
        final Set<ScheduledTask> scheduledTasks = (Set<ScheduledTask>)scheduledTasksField.get(taskRegistrar);
        if (scheduledTasks != null) {
            synchronized (scheduledTasks) {
                scheduledTasks.remove(scheduledTask);
            }
        }
    }

}
